//
//  ViewController.swift
//  OpenGLES-GLSL
//
//  Created by FG on 2018/5/10.
//  Copyright © 2018年 yongyida. All rights reserved.
//

import UIKit

import GLKit



class ViewController: GLKViewController,GLKViewControllerDelegate {

    var context: EAGLContext?
    var glkView: GLKView!
    
    var vao: GLuint = 0
    
    var program : GLuint = 0
    var modelViewMatrixUniform : Int32 = 0
    var projectionMatrixUniform : Int32 = 0
    var textureUniform : Int32 = 0
    
    var modelViewMatrix : GLKMatrix4 = GLKMatrix4Identity
    var projectionMatrix : GLKMatrix4 = GLKMatrix4Identity
    var texture: GLuint = 0
    
    // tansform
    var position : GLKVector3 = GLKVector3(v: (0.0, 0.0, -5.0))
    var rotationX : Float = 0.0
    var rotationY : Float = 0.0
    var rotationZ : Float = 0.0
    var scale : Float = 1.0
    
    var vertexs: [Vertex] {
        let arr = [
            // Front
            Vertex(Position: ( 1,-1, 1), Color: (1,0,0, 1), TexCoord: (0.25, 0)),
            Vertex(Position: ( 1, 1, 1), Color: (1,0,0, 1), TexCoord: (0.25, 0.25)),
            Vertex(Position: (-1, 1, 1), Color: (1,0,0, 1), TexCoord: (0,    0.25)),
            Vertex(Position: (-1,-1, 1), Color: (1,0,0, 1), TexCoord: (0,    0)),
            // Back
            Vertex(Position: (-1,-1,-1), Color: (1,0,0, 1), TexCoord: (0.5 , 0)),
            Vertex(Position: (-1, 1,-1), Color: (1,0,0, 1), TexCoord: (0.5 , 0.25)),
            Vertex(Position: ( 1, 1,-1), Color: (1,0,0, 1), TexCoord: (0.25, 0.25)),
            Vertex(Position: ( 1,-1,-1), Color: (1,0,0, 1), TexCoord: (0.25, 0)),
            
            // Left
            Vertex(Position: (-1,-1, 1), Color: (1,0,0, 1), TexCoord: (0.75, 0)),
            Vertex(Position: (-1, 1, 1), Color: (1,0,0, 1), TexCoord: (0.75, 0.25)),
            Vertex(Position: (-1, 1,-1), Color: (1,0,0, 1), TexCoord: (0.5, 0.25)),
            Vertex(Position: (-1,-1,-1), Color: (1,0,0, 1), TexCoord: (0.5, 0)),
            // Right
            Vertex(Position: ( 1,-1,-1), Color: (1,0,0, 1), TexCoord: (1, 0)),
            Vertex(Position: ( 1, 1,-1), Color: (1,0,0, 1), TexCoord: (1, 0.25)),
            Vertex(Position: ( 1, 1, 1), Color: (1,0,0, 1), TexCoord: (0.75, 0.25)),
            Vertex(Position: ( 1,-1, 1), Color: (1,0,0, 1), TexCoord: (0.75, 0)),
            
            // Top
            Vertex(Position: ( 1, 1, 1), Color: (1,0,0, 1), TexCoord: (0.25, 0.25)),
            Vertex(Position: ( 1, 1,-1), Color: (1,0,0, 1), TexCoord: (0.25, 0.5)),
            Vertex(Position: (-1, 1,-1), Color: (1,0,0, 1), TexCoord: (0, 0.5)),
            Vertex(Position: (-1, 1, 1), Color: (1,0,0, 1), TexCoord: (0, 0.25)),
            // Bottom
            Vertex(Position: ( 1,-1,-1), Color: (1,0,0, 1), TexCoord: (0.5, 0.25)),
            Vertex(Position: ( 1,-1, 1), Color: (1,0,0, 1), TexCoord: (0.5, 0.5)),
            Vertex(Position: (-1,-1, 1), Color: (1,0,0, 1), TexCoord: (0.25, 0.5)),
            Vertex(Position: (-1,-1,-1), Color: (1,0,0, 1), TexCoord: (0.25, 0.25)),
        ]
        return arr
    }
    
    let indexList : [GLubyte] = [
        
        // Front
        0, 1, 2,
        2, 3, 0,
        
        // Back
        4, 5, 6,
        6, 7, 4,
        
        // Left
        8, 9, 10,
        10, 11, 8,
        
        // Right
        12, 13, 14,
        14, 15, 12,
        
        // Top
        16, 17, 18,
        18, 19, 16,
        
        // Bottom
        20, 21, 22,
        22, 23, 20
    ]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.delegate = self
        
        setupContext()
        
        self.compile(vertexShader: "vertexShader.glsl", fragmentShader: "fragmentShader.glsl")
        
        setupVAO()
        
        // 加载纹理
        self.texture = self.loadTexture("dice.png")?.name ?? 0
        
        // 投影矩阵
        self.projectionMatrix = GLKMatrix4MakePerspective(
            GLKMathDegreesToRadians(85.0),
            GLfloat(self.view.bounds.size.width / self.view.bounds.size.height),
            1,
            150)
    }

    func setupContext() {
        
        self.glkView = self.view as! GLKView
        
        self.context = EAGLContext.init(api: .openGLES2)
        if context == nil {
            context = EAGLContext.init(api: .openGLES2)
        }
        
        guard let ctx = context else {
            return
        }
        EAGLContext.setCurrent(ctx) // 设置当前上下文
        
        self.glkView.context = ctx
        // 32位RGB颜色 （R+G+B+A）
        self.glkView.drawableColorFormat = .RGBA8888
        self.glkView.drawableDepthFormat = .format24
    }
    
    func setupVAO() {
        // 申请vao空间
        glGenVertexArraysOES(1, &vao)
        glBindVertexArrayOES(vao)
        // 顶点buffer
        var vertexBuffer: GLuint = 0
        glGenBuffers(1, &vertexBuffer)
        glBindBuffer(GLenum(GL_ARRAY_BUFFER), vertexBuffer)
        glBufferData(GLenum(GL_ARRAY_BUFFER), vertexs.size(), vertexs, GLenum(GL_STATIC_DRAW))
        // 索引buffer
        var indexBuffer: GLuint = 0
        glGenBuffers(1, &indexBuffer)
        glBindBuffer(GLenum(GL_ELEMENT_ARRAY_BUFFER), indexBuffer)
        glBufferData(GLenum(GL_ELEMENT_ARRAY_BUFFER), indexList.size(), indexList, GLenum(GL_STATIC_DRAW))
        // 顶点坐标
        let positionSlotFirstComponent = UnsafePointer<Int>(bitPattern: 0)
        glEnableVertexAttribArray(VertexAttributes.position.rawValue)
        glVertexAttribPointer(VertexAttributes.position.rawValue, 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<Vertex>.size), positionSlotFirstComponent)
        // 颜色坐标
        let colorSlotFirstComponent = UnsafePointer<Int>(bitPattern: MemoryLayout<GLfloat>.size * 3)
        glEnableVertexAttribArray(VertexAttributes.color.rawValue)
        glVertexAttribPointer(VertexAttributes.color.rawValue, 4, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<Vertex>.size), colorSlotFirstComponent)
        // 纹理坐标
        let texSlotFirstComponent = UnsafePointer<Int>(bitPattern: MemoryLayout<GLfloat>.size * 7)
        glEnableVertexAttribArray(VertexAttributes.texCoord.rawValue)
        glVertexAttribPointer(VertexAttributes.texCoord.rawValue, 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<Vertex>.size), texSlotFirstComponent)
        
        glBindVertexArrayOES(0)
        glBindBuffer(GLenum(GL_ARRAY_BUFFER), 0)
        glBindBuffer(GLenum(GL_ELEMENT_ARRAY_BUFFER), 0)
        
    }
    
    func compile(vertexShader: String, fragmentShader: String) {
        // 1.编译着色器
        let vertexShader = self.compileShader(vertexShader, shaderType: GLenum(GL_VERTEX_SHADER))
        let fragmentShader = self.compileShader(fragmentShader, shaderType: GLenum(GL_FRAGMENT_SHADER))
        
        // 2.创建程序
        self.program = glCreateProgram()
        // 3.将shader附加到程序中
        glAttachShader(program, vertexShader)
        glAttachShader(program, fragmentShader)
        
        glBindAttribLocation(program, VertexAttributes.position.rawValue, "a_Position") // 顶点坐标属性
        glBindAttribLocation(program, VertexAttributes.color.rawValue, "a_Color") // 颜色坐标属性
        glBindAttribLocation(program, VertexAttributes.texCoord.rawValue, "a_TexCoord") // 纹理坐标属性
        // 4.链接
        glLinkProgram(program)
        
        self.modelViewMatrixUniform = glGetUniformLocation(program, "u_ModelViewMatrix") // 模型视图矩阵
        self.projectionMatrixUniform = glGetUniformLocation(program, "u_ProjectionMatrix") // 投影矩阵
        self.textureUniform = glGetUniformLocation(program, "u_Texture") // 纹理
        
        var linkStatus : GLint = 0
        // 获取链接状态
        glGetProgramiv(self.program, GLenum(GL_LINK_STATUS), &linkStatus)
        if linkStatus == GL_FALSE {
            var infoLength : GLsizei = 0
            let bufferLength : GLsizei = 1024
            glGetProgramiv(self.program, GLenum(GL_INFO_LOG_LENGTH), &infoLength)
            
            let info : [GLchar] = Array(repeating: GLchar(0), count: Int(bufferLength))
            var actualLength : GLsizei = 0
            
            glGetProgramInfoLog(self.program, bufferLength, &actualLength, UnsafeMutablePointer(mutating: info))
            NSLog(String(validatingUTF8: info)!)
            exit(1)
        }
    }
    
    func compileShader(_ shaderName:String, shaderType:GLenum) ->GLuint {
        // shader file path
        let path = Bundle.main.path(forResource: shaderName, ofType: nil)
        do {
            // shader file content string
            let shaderString = try NSString(contentsOfFile: path!, encoding: String.Encoding.utf8.rawValue)
            // 根据类型创建一个shader
            let shaderHandle = glCreateShader(shaderType)
            var shaderStringLength:GLint = GLint(Int32(shaderString.length))
            var shaderCString = shaderString.utf8String
            /// 将shader源码附加带shader对象上
            ///
            /// - Parameters:
            ///   - shader: shader对象
            ///   - count: 源码字符串个数
            ///   - string: 源码字符串
            ///   - length: 源码字符串长度
            glShaderSource(shaderHandle, GLsizei(1), &shaderCString, &shaderStringLength)
            
            // 编译shader
            glCompileShader(shaderHandle)
            var compileStatus : GLint = 0
            // 获取编译状态
            glGetShaderiv(shaderHandle, GLenum(GL_COMPILE_STATUS), &compileStatus)
            
            if compileStatus == GL_FALSE { // 编译失败
                
                var infoLength : GLsizei = 0
                let bufferLength : GLsizei = 1024
                
                glGetShaderiv(shaderHandle, GLenum(GL_INFO_LOG_LENGTH), &infoLength)
                // 创建char数组
                let info : [GLchar] = Array(repeating: GLchar(0), count: Int(bufferLength))
                var actualLength : GLsizei = 0
                // 获取错误信息
                glGetShaderInfoLog(shaderHandle, bufferLength, &actualLength, UnsafeMutablePointer(mutating: info))
                NSLog(String(validatingUTF8: info)!)
                exit(1)
            }
            
            return shaderHandle
            
        } catch {
            exit(1)
        }
    }
    
    func prepareToDraw() {
        glUseProgram(self.program)
        
        
        //设置glsl里面的投影矩阵
        /*
         void glUniformMatrix4fv(GLint location,  GLsizei count,  GLboolean transpose,  const GLfloat *value);
         参数列表：
         location:指要更改的uniform变量的位置
         count:更改矩阵的个数
         transpose:是否要转置矩阵，并将它作为uniform变量的值。必须为GL_FALSE
         value:执行count个元素的指针，用来更新指定uniform变量
         */
        glUniformMatrix4fv(self.projectionMatrixUniform, 1, GLboolean(GL_FALSE), self.projectionMatrix.array)
        //设置glsl里面的模型视图矩阵
        glUniformMatrix4fv(self.modelViewMatrixUniform, 1, GLboolean(GL_FALSE), self.modelViewMatrix.array)
        
        // 유니폼 주입
        glActiveTexture(GLenum(GL_TEXTURE1))
        glBindTexture(GLenum(GL_TEXTURE_2D), self.texture)
        glUniform1i(self.textureUniform, 1)
    }
    
    func modelMatrix() -> GLKMatrix4 {
        var modelMatrix : GLKMatrix4 = GLKMatrix4Identity
        modelMatrix = GLKMatrix4Translate(modelMatrix, self.position.x, self.position.y, self.position.z)
        modelMatrix = GLKMatrix4Rotate(modelMatrix, self.rotationX, 1, 0, 0)
        modelMatrix = GLKMatrix4Rotate(modelMatrix, self.rotationY, 0, 1, 0)
        modelMatrix = GLKMatrix4Rotate(modelMatrix, self.rotationZ, 0, 0, 1)
        modelMatrix = GLKMatrix4Scale(modelMatrix, self.scale, self.scale, self.scale)
        return modelMatrix
    }
    
    override func glkView(_ view: GLKView, drawIn rect: CGRect) {
        
        glClearColor(1.0, 1.0, 1.0, 1.0)
        glClear(GLbitfield(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT))
        
        glEnable(GLenum(GL_DEPTH_TEST))
        // 模型矩阵
        self.modelViewMatrix = modelMatrix()
        // 准备绘制
        self.prepareToDraw()
        // render
        glBindVertexArrayOES(vao)
        glDrawElements(GLenum(GL_TRIANGLES), GLsizei(indexList.count), GLenum(GL_UNSIGNED_BYTE), nil)
        glBindVertexArrayOES(0)
    }
    
    // MARK: - GLKViewControllerDelegate
    func glkViewControllerUpdate(_ controller: GLKViewController) {
        let dt = controller.timeSinceLastUpdate
        self.rotationZ = self.rotationZ + Float(Double.pi*dt)
        self.rotationY = self.rotationY + Float(Double.pi*dt)
    }
}

extension ViewController {
    
    func loadTexture(_ filename: String) -> GLKTextureInfo? {
        
        let path = Bundle.main.path(forResource: filename, ofType: nil)!
        let option = [ GLKTextureLoaderOriginBottomLeft: NSNumber.init(value: true)]
        do {
            let info = try GLKTextureLoader.texture(withContentsOfFile: path, options: option)
            return info
        } catch let e {
            print("load texture error: \(e)")
        }
        
        return nil
    }
}


// Array meory extension
extension Array {
    func size () -> Int {
        return self.count * MemoryLayout<Element>.size
    }
}
// GLKMatrix extension
extension GLKMatrix2 {
    var array: [Float] {
        return (0..<4).map { i in
            self[i]
        }
    }
}


extension GLKMatrix3 {
    var array: [Float] {
        return (0..<9).map { i in
            self[i]
        }
    }
}

extension GLKMatrix4 {
    var array: [Float] {
        return (0..<16).map { i in
            self[i]
        }
    }
}
